#ifndef MTL_MUTEX_HPP
#define MTL_MUTEX_HPP

extern "C" {
#include <pthread.h>
}
#include "exception.hpp"

namespace mtl {

  class mutex {
  private:
    pthread_mutex_t m_m;
  private:
    mutex(const mutex&);
    mutex& operator=(const mutex&);
  protected:
    explicit mutex(const int type) {
      pthread_mutexattr_t attr;
      pthread_mutexattr_init(&attr);
      pthread_mutexattr_settype(&attr, type);
      init(&attr);
    }
    void init(pthread_mutexattr_t* attr) {
      pthread_mutex_init(&m_m, attr);
    }
  public:
    explicit mutex(pthread_mutexattr_t* attr=NULL) {
      init(attr);
    }
    virtual ~mutex() {
      pthread_mutex_destroy(&m_m);
    }
    void lock() {
      PTHREAD_VALIDATE(
        pthread_mutex_lock(&m_m),
          "failed to lock mutex");
    }
    bool try_lock() {
      const int err = pthread_mutex_trylock(&m_m);
      switch (err) {
      case 0:
        return true;
      case EBUSY:
        return false;
      }
      throw system_error(
          error_code(err, system_category()),
          "failed to try lock");
    }
    void unlock() {
      PTHREAD_VALIDATE(
        pthread_mutex_unlock(&m_m),
          "failed to lock mutex");
    }
    pthread_mutex_t& pmt() { return m_m; }
  };

  class recursive_mutex : public mutex {
  public:
    recursive_mutex() : mutex(PTHREAD_MUTEX_RECURSIVE) {}
  };

  template<class _Mutex> class lock_guard {
  private:
    lock_guard(const lock_guard<_Mutex>&);
    lock_guard<_Mutex>& operator=(const lock_guard<_Mutex>&);
  public:
    lock_guard(_Mutex& m) : m_m(m) {
      m_m.lock();
    }
    ~lock_guard() {
      m_m.unlock();
    }
    _Mutex& mutex() {
      return m_m;
    }
  private:
    _Mutex& m_m;
  };

  namespace lock_mode {
    struct defer_lock {};
  }
  const lock_mode::defer_lock defer_lock = {};

  template<class _Mutex> class unique_lock {
  private:
    unique_lock(const unique_lock<_Mutex>&);
    unique_lock<_Mutex>& operator=(const unique_lock<_Mutex>&);
  public:
    unique_lock(_Mutex& m) : m_m(m) {
      lock();
    }
    unique_lock(_Mutex& m, const lock_mode::defer_lock&) : m_m(m) {
    }
    ~unique_lock() {
      unlock();
    }
    void lock() {
      m_m.lock();
    }
    bool try_lock() {
      return m_m.try_lock();
    }
    void unlock() {
      m_m.unlock();
    }
    _Mutex& mutex() {
      return m_m;
    }
  private:
    _Mutex& m_m;
  };

}
#endif
